﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Reflection;

using BizElements.Core;

namespace BizElements.BusinessLayer
{
    /// <summary>Reflection based implementation of generic interface for managing children collections that belong to a composite entity object. 
    /// Assumes method naming conventions as implememented in BizElements XML templates that generate business object classes.</summary>
    public sealed class ReflectionChildrenAdapter : IChildrenAdapter
    {
        Type childBizObjectClass;
        DbRelation foreignKey;

        /// <summary>
        /// Initializes a new instance of <b>ReflectionChildrenAdapter</b> class.
        /// </summary>
        /// <param name="propertyName">Child entity collection property name. Property must be a collection that implement <b>IList</b> interface.</param>
        /// <param name="childBizObjectClass">Child entity class that implements <see cref="IDataObject"/> interface.</param>
        /// <param name="foreignKey">Foreign key connecting children entities to parent object.</param>
        public ReflectionChildrenAdapter(string propertyName, Type childBizObjectClass, DbRelation foreignKey)
        {
            this.PropertyName = propertyName;
            this.childBizObjectClass = childBizObjectClass;
            this.foreignKey = foreignKey;
        }

        /// <summary>Gets child collection property name.</summary>
        public string PropertyName { get; private set; }

        #region IChildrenAdapter methods.

        /// <summary>Gets relation through which child entities are connected to parent table.</summary>
        /// <returns>Database relation.</returns>
        public DbRelation GetForeignKey()
        {
            return this.foreignKey;
        }

        /// <summary>Gets child collection that belongs to the specified parent entity.</summary>
        /// <param name="parentEntity">Parent object that owns the child collection worked by current adapter.</param>
        /// <returns>Children entity collection.</returns>
        public IList GetChildren(IBusinessObject parentEntity)
        {
            return (IList)ReflectionUtil.GetPublicPropertyValue(parentEntity, this.PropertyName);
        }

        /// <summary>Fetches children that belong to the specified parent entity from data source.</summary>
        /// <param name="parentEntity">Parent object that owns the child collection wrapped by the current adapter.</param>
        public void FetchChildren(IBusinessObject parentEntity)
        {
            string methodName = string.Format("Fetch{0}", this.PropertyName);
            ReflectionUtil.InvokeMethod(parentEntity, methodName, new object[0]);
        }

        /// <summary>Ensures that the child collection has been fetched from data source.</summary>
        /// <param name="parentEntity">Parent object that owns the child collection wrapped by the current adapter.</param>
        public void EnsureChildren(IBusinessObject parentEntity)
        {
            string methodName = string.Format("Ensure{0}", this.PropertyName);
            ReflectionUtil.InvokeMethod(parentEntity, methodName, new object[0]);
        }

        /// <summary>Persists children to data source if partial saving of composite entity is allowed and implemented by provided composite entity.</summary>
        /// <param name="parentEntity">Parent object that owns the child collection wrapped by the current adapter.</param>
        /// <param name="actionInitiator">Action initiator. This may be an application user, software module, 3rd party application etc. which is initiating the save action. <b>Null</b> is not allowed.</param>
        /// <param name="enforceSecurityChecks">Indicates whether the actor's permissions to save the current object are to be checked. Set to <b>false</b> only if client is trusted and the security checks have been executed elsewhere.</param>
        /// <exception cref="BrokenRuleException">The given actor failed the security checks. Generated exception contains details about broken rules/permissions.</exception>
        public void SaveChildren(IBusinessObject parentEntity, IActor actionInitiator, bool enforceSecurityChecks)
        {
            if (enforceSecurityChecks)
            {
                var brokenSecurityRules = parentEntity.GrantSave(actionInitiator);
                if (brokenSecurityRules.HasBrokenRules)
                    throw new BrokenRuleException(brokenSecurityRules);
            }

            EnsureBusinessObjectsInChildCollection(parentEntity);
            foreach (IDataObject child in GetChildren(parentEntity))
                child.SetModifiedBy(actionInitiator);

            string methodName = string.Format("Save{0}", this.PropertyName);
            ReflectionUtil.InvokeMethod(parentEntity, methodName, new object[0]);
        }

        /// <summary>Validates child entities that belong to the specified parent entity if partial validation of composite entity is allowed and implemented.</summary>
        /// <param name="parentEntity">Parent object that owns the child collection wrapped by the current adapter.</param>
        /// <param name="level">Specifies the detail of validation.</param>
        /// <returns>Collection of broken rules.</returns>
        public BusinessRuleCollection ValidateChildren(IBusinessObject parentEntity, ValidationLevel level)
        {
            string methodName = string.Format("Validate{0}", this.PropertyName);
            return (BusinessRuleCollection)ReflectionUtil.InvokeMethod(parentEntity, methodName, new object[] { level });
        }

        /// <summary>Creates a new instance of child entity but it's not automatically added to the collection.</summary>
        /// <param name="parentEntity">Parent object that owns the child collection wrapped by the current adapter.</param>
        /// <param name="createdBy">Actor that creates a new object.</param>
        /// <returns>A child entity.</returns>
        public IDataObject NewChild(IBusinessObject parentEntity, IActor createdBy)
        {
            string methodName = string.Format("New{0}", this.PropertyName.Replace("Children", ""));
            IDataObject child = (IDataObject)ReflectionUtil.InvokeStaticMethod(parentEntity.GetType(), methodName, new object[] { (parentEntity as ISelfValidatingObject).MessageSource.CurrentCulture, parentEntity });
            child.SetModifiedBy(createdBy);
            return child;
        }

        /// <summary>Tries to ensuree that child collection contains objects that implement <see cref="IDataObject"/> interface. The method will attempt to convert simple, BizElements generated, entities to business objects.</summary>
        /// <param name="parentEntity">Parent object that owns the child collection wrapped by the current adapter.</param>
        public void EnsureBusinessObjectsInChildCollection(IBusinessObject parentEntity)
        {
            const string methodName = "ConvertEntitiesToBusinessObjects";
            IList childCollection = GetChildren(parentEntity);
            object[] methodParameters = new object[] { childCollection, (parentEntity as ISelfValidatingObject).MessageSource.CurrentCulture };
            MethodInfo convertStaticMethod = ReflectionUtil.FindStaticMethod(this.childBizObjectClass, methodName, methodParameters);
            if (convertStaticMethod != null)
                convertStaticMethod.Invoke(null, methodParameters);
        }

        /// <summary>Ensures that all parent entities for children are initialized, ie. fetched.</summary>
        /// <param name="parentEntity">Parent object that owns the child collection wrapped by the current adapter.</param>
        public void EnsureAllParentsForChildren(IBusinessObject parentEntity)
        {
            string methodName = string.Format("EnsureAllParentsFor{0}", this.PropertyName);
            ReflectionUtil.InvokeMethod(parentEntity, methodName, new object[0]);
        }

        /// <summary>Ensures that all specified parent entities for children are fetched, ie. fetched.</summary>
        /// <param name="parentEntity">Parent object that owns the child collection wrapped by the current adapter.</param>
        /// <param name="relationsToOtherParents">Relations to other parents.</param>
        public void EnsureParentsForChildren(IBusinessObject parentEntity, DbRelation[] relationsToOtherParents)
        {
            string methodName = string.Format("EnsureParentsFor{0}", this.PropertyName);
            ReflectionUtil.InvokeMethod(parentEntity, methodName, relationsToOtherParents);
        }

        #endregion

        #region Clone.

        /// <summary>Creates a new adapter that is a copy of the current instance.</summary>
        /// <returns>A new object that is a copy of the current instance.</returns>
        public IChildrenAdapter Clone()
        {
            ReflectionChildrenAdapter clone = new ReflectionChildrenAdapter(this.PropertyName, this.childBizObjectClass, this.foreignKey.Clone());
            return clone;
        }

        #endregion
    }
}